package fr.pud.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.HashSet;

import org.openstreetmap.gui.jmapviewer.Coordinate;

import fr.pud.client.i18n.LanguagesConstants;
import fr.pud.client.model.Address;

/**
 * This class converts the GPS coordinates to an address and vice versa.
 * @author Pierre Reliquet, Simon Devineau
 */
public class Conversion {
    /**
     * The constant for a geocoding error
     */
    public static final HashSet<Address> GEOCODING_ERROR        = null;
    /**
     * The time of the maximum connection to the nominatim server
     */
    public static final int              SECOND_TIME_CONNECTION = 2;
    /**
     * The real timeout connection
     */
    public static final int              TIMEOUT_CONNECTION     = SECOND_TIME_CONNECTION * 1000;
    /**
     * The number of results that nominatim will return
     */
    private static final int             NB_RESULTS_GEOCODING   = 3;
    /*
     * Here is defined the URL uses to make the geocoding
     */
    public static final String           URL_START              = "http://nominatim.openstreetmap.org/search?q=";
    public static final String           URL_END                = "&format=xml&polygon=0&addressdetails=1&limit="
                                                                        + NB_RESULTS_GEOCODING;

    /**
     * @param adressToSplit
     *            , the string to modify
     * @return a String which is modified to be more readable in a JLabel
     */
    public static String adressSplittingForJLabel(String adressToSplit) {
        String[] adresseTable = adressToSplit.split(",");
        String aReturn = "";
        for (int index = 0; index < adresseTable.length; index++) {
            if (adresseTable[index].startsWith(" ")) {
                adresseTable[index] = adresseTable[index].substring(1);
            }
            aReturn = aReturn + adresseTable[index] + ", ";
            if (index % 3 == 0 && index != 0) {
                aReturn += "\n";
            }
        }
        aReturn = aReturn.substring(0, aReturn.lastIndexOf(","));
        try {
            aReturn = new String(aReturn.getBytes(), "UTF-8");
        }
        catch (UnsupportedEncodingException e) {
            Tools.writeToLogFile(LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.CONVERSION_UTF8), false, e
                    .getStackTrace());
        }
        return aReturn;
    }

    /**
     * @param address
     *            , the address which we want the GPS coordinates
     * @return, the coordinates
     */
    public static HashSet<Address> geocodingDeprecated(String address) {
        // The index of the string "lat="
        int latIndex;
        // The index of the string "lon="
        int lonIndex;
        // The index of the string where the longitude value finished
        int lonEndIndex = 0;
        int index = 0;
        String displayName = "display_name='";
        // The index of the string displayName
        int nameIndex = 0;
        // The index of the string where the display-name value finished
        int nameEndIndex = 0;
        // The arrayList to return
        HashSet<Address> addressList = new HashSet<Address>();
        /*
         * Here is defined the URL uses to make the geocoding
         */
        String urlStart = "http://nominatim.openstreetmap.org/search?q=";
        String urlEnd = "&format=xml&polygon=0&addressdetails=0&limit="
                + NB_RESULTS_GEOCODING;
        address = address.replace(" ", "+");
        String urlComplete = urlStart + address + urlEnd;
        String responseString = getResponseFromNominatim(urlComplete);
        /*
         * Getting the coordinates latitude & longitude
         */
        // If there is no latitude for the first index, nominatim does
        // not find results
        while (index < NB_RESULTS_GEOCODING) {
            latIndex = responseString.indexOf("lat=");
            lonIndex = responseString.indexOf("lon=");
            nameIndex = responseString.indexOf(displayName)
                    + displayName.length();
            nameEndIndex = responseString.indexOf("' class");
            lonEndIndex = lonIndex;
            if (!responseString.contains("lat=")) {
                // It means that there is no result
                if (index == 0) {
                    Tools.writeToLogFile(Address.ERROR_ADDRESS, true);
                    return GEOCODING_ERROR;
                }
                // It means that we got all the results
                else {
                    return addressList;
                }
            }
            // Calculate the lonIndexValue
            while (lonEndIndex < responseString.length()
                    && responseString.charAt(lonEndIndex) != ' ') {
                lonEndIndex++;
            }
            // Getting strings containing only the latitude or longitude,
            // example: xx.xxxxxx and not lat='xx.xxxxxx'
            String lat = responseString.substring(latIndex, lonIndex);
            lat = lat.substring(lat.indexOf('\'') + 1,
                    lat.indexOf('\'', lat.indexOf('\'') + 1));
            String lon = responseString.substring(lonIndex, lonEndIndex);
            lon = lon.substring(lon.indexOf('\'') + 1,
                    lon.indexOf('\'', lon.indexOf('\'') + 1));
            // Getting the name of the corresponding coordinate lat et lon
            String name = responseString.substring(nameIndex, nameEndIndex);
            name = new String(name.getBytes(), Charset.forName("UTF-8"));
            double latDouble = Double.parseDouble(lat);
            double lonDouble = Double.parseDouble(lon);
            String response = responseString.substring(responseString
                    .indexOf("osm_id="));
            long id = Long.parseLong(response.substring(
                    response.indexOf("'") + 1, response.indexOf("' ")));
            addressList.add(new Address(name, new Coordinate(latDouble,
                    lonDouble), id));
            index++;
            responseString = responseString.substring(nameEndIndex + 4);
        }
        return addressList;
    }

    public static final String PLACE            = "<place";
    public static final String END_PLACE        = "</place>";
    public static final String LATITUDE         = "lat=\'";
    public static final String LONGITUDE        = "lon=\'";
    public static final String OSM_ID           = "osm_id=\'";
    public static final String HOUSE_NUMBER     = "<house_number>";
    public static final String END_HOUSE_NUMBER = "</house_number>";
    public static final String ROAD             = "<road>";
    public static final String END_ROAD         = "</road>";
    public static final String CITY             = "<city>";
    public static final String END_CITY         = "</city>";
    public static final String POSTCODE         = "<postcode>";
    public static final String END_POSTCODE     = "</postcode>";
    public static final String COUNTRY          = "<country>";
    public static final String END_COUNTRY      = "</country>";
    public static final char   QUOTE            = '\'';

    public static Address parseNominatimPlace(String place) {
        // Getting OSM ID
        String shortened = place.substring(place.indexOf(OSM_ID)
                + OSM_ID.length());
        long osmId = Long.parseLong(shortened.substring(0,
                shortened.indexOf(QUOTE)));
        // Getting latitude
        shortened = shortened.substring(shortened.indexOf(LATITUDE)
                + LATITUDE.length());
        double latitude = Double.parseDouble(shortened.substring(0,
                shortened.indexOf(QUOTE)));
        // Getting longitude
        shortened = shortened.substring(shortened.indexOf(LONGITUDE)
                + LONGITUDE.length());
        double longitude = Double.parseDouble(shortened.substring(0,
                shortened.indexOf(QUOTE)));
        // Variable used to construc the address
        int houseNumber = 0;
        String road = "";
        String city = "";
        int postcode = 0;
        String country = "";
        /*
         * If the string contains our info we get that else we jump it.
         */
        // Getting house number
        if (shortened.contains(HOUSE_NUMBER)) {
            shortened = shortened.substring(shortened.indexOf(HOUSE_NUMBER)
                    + HOUSE_NUMBER.length());
            houseNumber = Integer.parseInt(shortened.substring(0,
                    shortened.indexOf(END_HOUSE_NUMBER)));
        }
        // Getting road name
        if (shortened.contains(ROAD)) {
            shortened = shortened.substring(shortened.indexOf(ROAD)
                    + ROAD.length());
            road = shortened.substring(0, shortened.indexOf(END_ROAD));
        }
        // Getting city name
        if (shortened.contains(CITY)) {
            shortened = shortened.substring(shortened.indexOf(CITY)
                    + CITY.length());
            city = shortened.substring(0, shortened.indexOf(END_CITY));
        }
        // Getting postcode
        if (shortened.contains(POSTCODE)) {
            shortened = shortened.substring(shortened.indexOf(POSTCODE)
                    + POSTCODE.length());
            postcode = Integer.parseInt(shortened.substring(0,
                    shortened.indexOf(END_POSTCODE)));
        }
        // Getting country
        if (shortened.contains(COUNTRY)) {
            shortened = shortened.substring(shortened.indexOf(COUNTRY)
                    + COUNTRY.length());
            country = shortened.substring(0, shortened.indexOf(END_COUNTRY));
        }
        // Constructing the address
        StringBuffer address = new StringBuffer();
        if (houseNumber != 0)
            address.append(houseNumber + " ");
        address.append(road + " ");
        if (postcode != 0)
            address.append(postcode + " ");
        address.append(city + ", " + country);
        // Creation of the station
        return new Address(address.toString(), new Coordinate(latitude,
                longitude), osmId);
    }

    /**
     * @param address
     *            , the address which we want the GPS coordinates
     * @return, the coordinates
     */
    public static HashSet<Address> geocoding(String address) {
        // The set to return
        HashSet<Address> addresses = new HashSet<Address>();
        address = address.replace(" ", "+");
        String urlComplete = URL_START + address + URL_END;
        String responseString = getResponseFromNominatim(urlComplete);
        if (!responseString.contains(PLACE))
            return GEOCODING_ERROR;
        while (responseString.contains(PLACE)) {
            String place = responseString.substring(
                    responseString.indexOf(PLACE) + PLACE.length(),
                    responseString.indexOf(END_PLACE));
            addresses.add(parseNominatimPlace(place));
            responseString = responseString.substring(responseString
                    .indexOf(END_PLACE) + END_PLACE.length());
        }
        return addresses;
    }

    private static String getResponseFromNominatim(String url) {
        String line;
        String response = "";
        try {
            /*
             * Connection + get response
             */
            URL nominatim = new URL(url);
            URLConnection urlConnect = nominatim.openConnection();
            urlConnect.setConnectTimeout(TIMEOUT_CONNECTION);
            InputStream responseStream = urlConnect.getInputStream();
            BufferedReader readingResponse = new BufferedReader(
                    new InputStreamReader(responseStream));
            /*
             * Reading the response made of streams
             */
            boolean asEnded = false;
            while ((line = readingResponse.readLine()) != null && !asEnded) {
                response += line + "\n";
                if (response.contains("</result>")
                        || response.contains("</searchresults>")) {
                    asEnded = true;
                }
            }
        }
        catch (MalformedURLException e) {
            Tools.writeToLogFile(LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.WRONG_URL),
                    Tools.LOG_FILE_NO_JOPTION_PANE, e.getStackTrace());
            e.printStackTrace();
        }
        catch (IOException io) {
            Tools.writeToLogFile(
                    LanguagesConstants.LANGUAGE
                            .getString(LanguagesConstants.SERVER_UNREACHABLE_NOMINATIM),
                    Tools.LOG_FILE_NO_JOPTION_PANE, io.getStackTrace());
        }
        return response;
    }

    /**
     * @param c
     *            , the coordinates of the address wanted
     * @return an address corresponding to the coordinates
     */
    public static IdOSM reverseGeocoding(Coordinate c) {
        /*
         * Here is defined the URL uses to make the geocoding
         */
        String urlStart = "http://nominatim.openstreetmap.org/reverse?format=xml&";
        String urlEnd = "&zoom=18&addressdetails=0";
        String coord = "lat=" + c.getLat() + "&lon=" + c.getLon();
        String urlComplete = urlStart + coord + urlEnd;
        /*
         * Connection + get response
         */
        String response = Conversion.getResponseFromNominatim(urlComplete);
        String address = "null";
        long id = 0;
        if (response.contains("</result>")) {
            response = response.substring(response.indexOf("<result"),
                    response.indexOf("</result>"));
            address = response.substring(response.indexOf("\">") + 2);
            response = response.substring(response.indexOf("osm_id="));
            id = Long.parseLong(response.substring(response.indexOf("\"") + 1,
                    response.indexOf("\" ")));
        }
        return new IdOSM(id, address);
    }
}
